Add: partial-good-aware AICPU thread resolution (a2a3/a5) - #1521
Add: partial-good-aware AICPU thread resolution (a2a3/a5)#1521yanghaoran29 wants to merge 1 commit into
Conversation
|
Important Review skippedAuto incremental reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughAICPU thread configuration now uses ChangesAICPU auto-resolution
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant TestCase
participant DeviceRunner
participant TopologyProbe
participant Runtime
TestCase->>DeviceRunner: submit config with aicpu_thread_num=0
DeviceRunner->>TopologyProbe: probe usable AICPU cores
TopologyProbe-->>DeviceRunner: return usable count
DeviceRunner->>Runtime: write resolved thread count
Runtime-->>TestCase: launch with resolved configuration
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
3a46dec to
0f7adad
Compare
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/a2a3/platform/onboard/host/device_runner.cpp`:
- Around line 330-337: Keep the resolved AICPU count consistent across runtime
and launch/setup state: in src/a2a3/platform/onboard/host/device_runner.cpp
lines 330-337, propagate resolved_aicpu to launch_aicpu_num or use the updated
Runtime getter for subsequent calls; in
src/a2a3/platform/sim/host/device_runner.cpp lines 234-239 and
src/a5/platform/sim/host/device_runner.cpp lines 217-222, write each resolved
fallback value back via runtime.set_aicpu_thread_num(...).
In `@src/a2a3/platform/sim/host/device_runner.cpp`:
- Around line 234-239: After resolving the zero-value fallback in the launch
flow, write the resulting launch_aicpu_num back to the Runtime object before it
is passed to AICPU execution and setup. Update the existing runtime thread-count
state used by get_aicpu_thread_num(), while preserving configured nonzero values
and the PLATFORM_DEFAULT_AICPU_THREAD_NUM fallback.
In `@src/a5/platform/onboard/host/device_runner.cpp`:
- Around line 159-163: Update the launch_aicpu_num normalization in the device
runner to treat only aicpu_thread_num equal to zero as auto; preserve negative
values so validate_launch_aicpu_num() rejects them instead of converting them to
PLATFORM_DEFAULT_AICPU_THREAD_NUM.
- Around line 159-163: After the AICPU resolution logic that computes
resolved_aicpu, update Runtime unconditionally via
runtime.set_aicpu_thread_num(resolved_aicpu). Place this after the allowed-CPU
clamping path so both configured and auto modes publish the effective active
count, including when no adjustment branch is taken.
In `@src/a5/platform/sim/host/device_runner.cpp`:
- Around line 217-222: After resolving the platform default in the launch setup,
write the resulting launch_aicpu_num back through the Runtime setter so
runtime.get_aicpu_thread_num() matches the count used by init_l2_swimlane and
AICPU execution. Keep the existing explicit-count behavior unchanged.
In `@src/common/platform/onboard/host/device_runner_base.cpp`:
- Around line 1168-1174: Update the clamp warning in the thread-count
calculation to log desired instead of requested, so auto mode reports the
architecture default selected by the desired calculation. Preserve the existing
warning text, usable count, and scheduler count.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 8a54c647-7f3b-42da-ab4b-d4cd2bea8eac
📒 Files selected for processing (92)
examples/a2a3/tensormap_and_ringbuffer/benchmark_bgemm/test_benchmark_bgemm.pyexamples/a2a3/tensormap_and_ringbuffer/merge_pipeline_barrier/test_merge_pipeline_barrier.pyexamples/a2a3/tensormap_and_ringbuffer/paged_attention/test_paged_attention.pyexamples/a2a3/tensormap_and_ringbuffer/paged_attention_manual_scope/test_paged_attention.pyexamples/a2a3/tensormap_and_ringbuffer/paged_attention_ringbuffer/test_paged_attention_ringbuffer.pyexamples/a2a3/tensormap_and_ringbuffer/paged_attention_unroll_manual_scope/test_paged_attention_unroll.pyexamples/a2a3/tensormap_and_ringbuffer/qwen3_14b_decode/test_qwen3_14b_decode.pyexamples/a2a3/tensormap_and_ringbuffer/scalar_data_test/test_scalar_data.pyexamples/a2a3/tensormap_and_ringbuffer/vector_example/test_vector_example.pyexamples/a5/tensormap_and_ringbuffer/bgemm/test_bgemm.pyexamples/a5/tensormap_and_ringbuffer/paged_attention/test_paged_attention.pyexamples/a5/tensormap_and_ringbuffer/paged_attention_manual_scope/test_paged_attention.pyexamples/a5/tensormap_and_ringbuffer/paged_attention_unroll_manual_scope/test_paged_attention_unroll.pyexamples/a5/tensormap_and_ringbuffer/vector_example/test_vector_example.pypython/simpler/task_interface.pysimpler_setup/scene_test.pysrc/a2a3/docs/hardware.mdsrc/a2a3/platform/include/common/platform_config.hsrc/a2a3/platform/onboard/host/device_runner.cppsrc/a2a3/platform/sim/host/device_runner.cppsrc/a5/docs/hardware.mdsrc/a5/platform/include/common/platform_config.hsrc/a5/platform/onboard/host/device_runner.cppsrc/a5/platform/sim/host/device_runner.cppsrc/common/platform/onboard/host/device_runner_base.cppsrc/common/platform/onboard/host/device_runner_base.hsrc/common/platform/sim/host/device_runner_base.cppsrc/common/task_interface/call_config.htests/st/a2a3/host_build_graph/available_aicore_counts/test_available_aicore_counts.pytests/st/a2a3/host_build_graph/bgemm/test_bgemm.pytests/st/a2a3/host_build_graph/dfx/dep_gen/test_dep_gen.pytests/st/a2a3/host_build_graph/dump_args/test_dump_args_example.pytests/st/a2a3/host_build_graph/matmul/test_matmul.pytests/st/a2a3/host_build_graph/paged_attention/test_paged_attention.pytests/st/a2a3/host_build_graph/prepared_callable/test_prepared_callable.pytests/st/a2a3/host_build_graph/run_stream_reuse/test_run_stream_reuse.pytests/st/a2a3/host_build_graph/vector_example/test_vector_example.pytests/st/a2a3/tensormap_and_ringbuffer/alternating_matmul_add/test_alternating_matmul_add.pytests/st/a2a3/tensormap_and_ringbuffer/available_aicore_counts/test_available_aicore_counts.pytests/st/a2a3/tensormap_and_ringbuffer/batch_paged_attention/test_batch_paged_attention.pytests/st/a2a3/tensormap_and_ringbuffer/dfx/args_dump/test_args_dump.pytests/st/a2a3/tensormap_and_ringbuffer/dfx/dep_gen/test_dep_gen.pytests/st/a2a3/tensormap_and_ringbuffer/dfx/l2_swimlane/test_l2_swimlane.pytests/st/a2a3/tensormap_and_ringbuffer/dfx/l2_swimlane/test_l2_swimlane_mixed.pytests/st/a2a3/tensormap_and_ringbuffer/dfx/pmu/test_pmu.pytests/st/a2a3/tensormap_and_ringbuffer/dfx/scope_stats/test_scope_stats.pytests/st/a2a3/tensormap_and_ringbuffer/dynamic_register/test_dynamic_register.pytests/st/a2a3/tensormap_and_ringbuffer/fanin_lookup_perf/test_fanin_lookup_perf.pytests/st/a2a3/tensormap_and_ringbuffer/mixed_example/test_mixed_example.pytests/st/a2a3/tensormap_and_ringbuffer/multi_round_paged_attention/test_multi_round_paged_attention.pytests/st/a2a3/tensormap_and_ringbuffer/orch_so_cache/test_orch_so_cache.pytests/st/a2a3/tensormap_and_ringbuffer/paged_attention_unroll/test_paged_attention_unroll.pytests/st/a2a3/tensormap_and_ringbuffer/paged_attention_unroll_4dims/test_paged_attention_unroll_4dims.pytests/st/a2a3/tensormap_and_ringbuffer/prepared_callable/test_prepared_callable.pytests/st/a2a3/tensormap_and_ringbuffer/spmd_basic/test_spmd_basic.pytests/st/a2a3/tensormap_and_ringbuffer/spmd_batch_dispatch_oob/test_spmd_batch_dispatch_oob.pytests/st/a2a3/tensormap_and_ringbuffer/spmd_multiblock_aiv/test_spmd_multiblock_aiv.pytests/st/a2a3/tensormap_and_ringbuffer/spmd_multiblock_mix/test_spmd_multiblock_mix.pytests/st/a2a3/tensormap_and_ringbuffer/spmd_paged_attention/test_spmd_paged_attention.pytests/st/a2a3/tensormap_and_ringbuffer/spmd_paged_attention_highperf/test_spmd_paged_attention_highperf.pytests/st/a2a3/tensormap_and_ringbuffer/spmd_starvation/test_spmd_starvation.pytests/st/a2a3/tensormap_and_ringbuffer/spmd_sync_start/test_spmd_sync_start.pytests/st/a2a3/tensormap_and_ringbuffer/spmd_sync_start_aiv/test_spmd_sync_start_aiv.pytests/st/a2a3/tensormap_and_ringbuffer/spmd_sync_start_early_dispatch/test_spmd_sync_start_early_dispatch.pytests/st/a2a3/tensormap_and_ringbuffer/spmd_sync_start_edge/test_spmd_sync_start_edge.pytests/st/a2a3/tensormap_and_ringbuffer/spmd_sync_start_stress/test_spmd_sync_start_stress.pytests/st/a2a3/tensormap_and_ringbuffer/test_l3_dependency.pytests/st/a2a3/tensormap_and_ringbuffer/test_l3_group.pytests/st/a5/tensormap_and_ringbuffer/available_aicore_counts/test_available_aicore_counts.pytests/st/a5/tensormap_and_ringbuffer/dfx/args_dump/test_args_dump.pytests/st/a5/tensormap_and_ringbuffer/dfx/dep_gen/test_dep_gen.pytests/st/a5/tensormap_and_ringbuffer/dfx/l2_swimlane/test_l2_swimlane.pytests/st/a5/tensormap_and_ringbuffer/dfx/l2_swimlane/test_l2_swimlane_mixed.pytests/st/a5/tensormap_and_ringbuffer/dfx/pmu/test_pmu.pytests/st/a5/tensormap_and_ringbuffer/dfx/scope_stats/test_scope_stats.pytests/st/a5/tensormap_and_ringbuffer/mixed_example/test_mixed_example.pytests/st/a5/tensormap_and_ringbuffer/orch_so_cache/test_orch_so_cache.pytests/st/a5/tensormap_and_ringbuffer/paged_attention_unroll/test_paged_attention_unroll.pytests/st/a5/tensormap_and_ringbuffer/prepared_callable/test_prepared_callable.pytests/st/a5/tensormap_and_ringbuffer/simt_basic/test_simt_basic.pytests/st/a5/tensormap_and_ringbuffer/spmd_basic/test_spmd_basic.pytests/st/a5/tensormap_and_ringbuffer/spmd_multiblock_aiv/test_spmd_multiblock_aiv.pytests/st/a5/tensormap_and_ringbuffer/spmd_multiblock_mix/test_spmd_multiblock_mix.pytests/st/a5/tensormap_and_ringbuffer/spmd_starvation/test_spmd_starvation.pytests/st/a5/tensormap_and_ringbuffer/spmd_sync_start/test_spmd_sync_start.pytests/st/a5/tensormap_and_ringbuffer/spmd_sync_start_aiv/test_spmd_sync_start_aiv.pytests/st/a5/tensormap_and_ringbuffer/spmd_sync_start_early_dispatch/test_spmd_sync_start_early_dispatch.pytests/st/a5/tensormap_and_ringbuffer/spmd_sync_start_edge/test_spmd_sync_start_edge.pytests/st/a5/tensormap_and_ringbuffer/spmd_sync_start_mix_spill/test_spmd_sync_start_mix_spill.pytests/st/a5/tensormap_and_ringbuffer/spmd_sync_start_stress/test_spmd_sync_start_stress.pytests/st/task_timing/task_timing_slots/test_task_timing_e2e.pytests/ut/py/test_chip_worker.py
💤 Files with no reviewable changes (72)
- tests/st/a5/tensormap_and_ringbuffer/spmd_starvation/test_spmd_starvation.py
- examples/a2a3/tensormap_and_ringbuffer/benchmark_bgemm/test_benchmark_bgemm.py
- tests/st/a2a3/tensormap_and_ringbuffer/alternating_matmul_add/test_alternating_matmul_add.py
- examples/a2a3/tensormap_and_ringbuffer/merge_pipeline_barrier/test_merge_pipeline_barrier.py
- examples/a5/tensormap_and_ringbuffer/vector_example/test_vector_example.py
- tests/st/a2a3/tensormap_and_ringbuffer/dfx/l2_swimlane/test_l2_swimlane.py
- tests/st/a5/tensormap_and_ringbuffer/mixed_example/test_mixed_example.py
- examples/a2a3/tensormap_and_ringbuffer/scalar_data_test/test_scalar_data.py
- tests/st/a5/tensormap_and_ringbuffer/spmd_sync_start_aiv/test_spmd_sync_start_aiv.py
- tests/st/a5/tensormap_and_ringbuffer/spmd_multiblock_aiv/test_spmd_multiblock_aiv.py
- tests/st/a5/tensormap_and_ringbuffer/spmd_sync_start/test_spmd_sync_start.py
- tests/st/a5/tensormap_and_ringbuffer/available_aicore_counts/test_available_aicore_counts.py
- tests/st/a5/tensormap_and_ringbuffer/dfx/pmu/test_pmu.py
- tests/st/a2a3/tensormap_and_ringbuffer/spmd_sync_start/test_spmd_sync_start.py
- tests/st/a2a3/tensormap_and_ringbuffer/spmd_sync_start_aiv/test_spmd_sync_start_aiv.py
- tests/st/a2a3/tensormap_and_ringbuffer/available_aicore_counts/test_available_aicore_counts.py
- tests/st/a5/tensormap_and_ringbuffer/dfx/args_dump/test_args_dump.py
- tests/st/a5/tensormap_and_ringbuffer/dfx/l2_swimlane/test_l2_swimlane.py
- examples/a2a3/tensormap_and_ringbuffer/paged_attention_ringbuffer/test_paged_attention_ringbuffer.py
- tests/st/a2a3/tensormap_and_ringbuffer/spmd_starvation/test_spmd_starvation.py
- tests/st/a5/tensormap_and_ringbuffer/spmd_basic/test_spmd_basic.py
- tests/st/a5/tensormap_and_ringbuffer/dfx/l2_swimlane/test_l2_swimlane_mixed.py
- tests/st/a2a3/tensormap_and_ringbuffer/spmd_multiblock_mix/test_spmd_multiblock_mix.py
- tests/st/a2a3/host_build_graph/bgemm/test_bgemm.py
- tests/st/a5/tensormap_and_ringbuffer/prepared_callable/test_prepared_callable.py
- tests/st/a5/tensormap_and_ringbuffer/spmd_sync_start_early_dispatch/test_spmd_sync_start_early_dispatch.py
- tests/st/a5/tensormap_and_ringbuffer/dfx/scope_stats/test_scope_stats.py
- tests/st/a2a3/tensormap_and_ringbuffer/dfx/scope_stats/test_scope_stats.py
- tests/st/a2a3/host_build_graph/vector_example/test_vector_example.py
- tests/st/a2a3/tensormap_and_ringbuffer/spmd_sync_start_early_dispatch/test_spmd_sync_start_early_dispatch.py
- examples/a2a3/tensormap_and_ringbuffer/qwen3_14b_decode/test_qwen3_14b_decode.py
- tests/st/a5/tensormap_and_ringbuffer/spmd_sync_start_mix_spill/test_spmd_sync_start_mix_spill.py
- tests/st/a2a3/tensormap_and_ringbuffer/dfx/l2_swimlane/test_l2_swimlane_mixed.py
- tests/st/a5/tensormap_and_ringbuffer/paged_attention_unroll/test_paged_attention_unroll.py
- tests/st/a2a3/host_build_graph/matmul/test_matmul.py
- examples/a2a3/tensormap_and_ringbuffer/vector_example/test_vector_example.py
- tests/st/a2a3/tensormap_and_ringbuffer/spmd_sync_start_edge/test_spmd_sync_start_edge.py
- tests/st/a2a3/host_build_graph/available_aicore_counts/test_available_aicore_counts.py
- tests/st/a2a3/tensormap_and_ringbuffer/dfx/args_dump/test_args_dump.py
- tests/st/a2a3/tensormap_and_ringbuffer/spmd_sync_start_stress/test_spmd_sync_start_stress.py
- tests/st/a2a3/tensormap_and_ringbuffer/spmd_batch_dispatch_oob/test_spmd_batch_dispatch_oob.py
- examples/a2a3/tensormap_and_ringbuffer/paged_attention_unroll_manual_scope/test_paged_attention_unroll.py
- tests/st/a2a3/tensormap_and_ringbuffer/multi_round_paged_attention/test_multi_round_paged_attention.py
- tests/st/a2a3/host_build_graph/prepared_callable/test_prepared_callable.py
- tests/st/a2a3/tensormap_and_ringbuffer/dfx/pmu/test_pmu.py
- tests/st/a5/tensormap_and_ringbuffer/spmd_sync_start_stress/test_spmd_sync_start_stress.py
- examples/a5/tensormap_and_ringbuffer/paged_attention_manual_scope/test_paged_attention.py
- tests/st/a5/tensormap_and_ringbuffer/spmd_multiblock_mix/test_spmd_multiblock_mix.py
- tests/st/a2a3/host_build_graph/dump_args/test_dump_args_example.py
- tests/st/a2a3/tensormap_and_ringbuffer/paged_attention_unroll/test_paged_attention_unroll.py
- examples/a2a3/tensormap_and_ringbuffer/paged_attention/test_paged_attention.py
- tests/st/a2a3/host_build_graph/paged_attention/test_paged_attention.py
- tests/st/a2a3/tensormap_and_ringbuffer/dfx/dep_gen/test_dep_gen.py
- tests/st/a2a3/tensormap_and_ringbuffer/mixed_example/test_mixed_example.py
- examples/a5/tensormap_and_ringbuffer/bgemm/test_bgemm.py
- tests/st/a2a3/tensormap_and_ringbuffer/paged_attention_unroll_4dims/test_paged_attention_unroll_4dims.py
- tests/st/a2a3/tensormap_and_ringbuffer/spmd_basic/test_spmd_basic.py
- tests/st/a5/tensormap_and_ringbuffer/spmd_sync_start_edge/test_spmd_sync_start_edge.py
- tests/st/a5/tensormap_and_ringbuffer/orch_so_cache/test_orch_so_cache.py
- tests/st/a2a3/tensormap_and_ringbuffer/spmd_paged_attention_highperf/test_spmd_paged_attention_highperf.py
- tests/st/a5/tensormap_and_ringbuffer/dfx/dep_gen/test_dep_gen.py
- examples/a5/tensormap_and_ringbuffer/paged_attention_unroll_manual_scope/test_paged_attention_unroll.py
- tests/st/task_timing/task_timing_slots/test_task_timing_e2e.py
- tests/st/a2a3/host_build_graph/dfx/dep_gen/test_dep_gen.py
- examples/a5/tensormap_and_ringbuffer/paged_attention/test_paged_attention.py
- tests/st/a2a3/tensormap_and_ringbuffer/spmd_multiblock_aiv/test_spmd_multiblock_aiv.py
- tests/st/a2a3/tensormap_and_ringbuffer/orch_so_cache/test_orch_so_cache.py
- tests/st/a2a3/tensormap_and_ringbuffer/fanin_lookup_perf/test_fanin_lookup_perf.py
- tests/st/a2a3/tensormap_and_ringbuffer/spmd_paged_attention/test_spmd_paged_attention.py
- tests/st/a2a3/tensormap_and_ringbuffer/dynamic_register/test_dynamic_register.py
- tests/st/a2a3/tensormap_and_ringbuffer/batch_paged_attention/test_batch_paged_attention.py
- tests/st/a2a3/tensormap_and_ringbuffer/prepared_callable/test_prepared_callable.py
83d054d to
59d5824
Compare
sunkaixuan2018
left a comment
There was a problem hiding this comment.
The auto/partial-good direction is useful, but I think a few issues need to be addressed or clarified:
-
The a5 usable count is not the device-visible AICPU pool.
The host probe reads host-sideOCCUPY=0x1feand obtains 8 CPUs, while the documented device-side pool is0x1f8with only 6 schedulable CPUs. Although the default active count is 5,aicpu_launch_countis still set to 8. The existing hardware documentation states that launching more than the device-side OCCUPY popcount can stall the production affinity gate and lead to a timeout. Both the active count and launch count should be derived from the actual device-visible pool. -
The thread-count and CPU-selection policy should consider cluster affinity.
resolve_aicpu_thread_num()currently only uses the total number of usable CPUs. Under PG, the remaining CPUs may be distributed across multiple clusters. For example, if four CPUs remain in a 2+2 distribution, the resolver still selects four threads and the affinity selector falls back to a cross-cluster layout. This may reintroduce the NUMA-crossing performance regression addressed by #1045/#1119.I suggest evaluating this on the current CPU architecture by marking different CPU cores as unavailable to emulate representative PG layouts, such as CPUs concentrated in one cluster or distributed across multiple clusters. Performance tests can then be used to compare different thread counts and placements and determine the best policy for each scenario—for example, whether keeping Scheduler threads, and preferably the orchestrator, in the same cluster is better than using more threads across clusters. The deterministic selector and tests from #1119 can be used as a reference. If a complete topology-aware policy is too complex for this PR, it would be reasonable to document the limitation and optimize it in a follow-up PR.
-
The new a5 failure path leaks the AICore register buffer.
init_aicore_register_addresses()allocateskernel_args_.args.regsbefore topology resolution, butregs_cleanupis created only afterwards. Returns caused by probe failure,usable < 2, or affinity-selection failure therefore skip cleanup. The guard should be installed before the allocation, as in the a2a3 implementation, or topology resolution should happen before allocating the buffer. -
aicpu_thread_num=1is accepted but is not a valid normal-run configuration.
On a5 onboard it skips topology setup, after which the device kernel rejects the missing affinity inputs. In the tensormap runtime it also means one orchestrator and zero schedulers. The public configuration should probably accept only0(auto) or[2, MAX]; any internal one-thread initialization path should be kept separate. -
The core PG behavior needs deterministic tests.
The current test changes mainly remove explicit thread counts and exercise the healthy auto path indirectly. Please add host-side tests forusable < default,usable < 2, explicit values larger than usable,aicpu_thread_num=1, and PG layouts distributed across multiple clusters. The tests should also verify that the resolved active count, selected CPUs, Runtime metadata, DFX counts, and actual launch count remain consistent.
There are also several user-facing documents that still list the default as 3, and the generic “1 orchestrator + N schedulers” wording does not match host_build_graph, where the device-side thread roles differ.
Use zero as the automatic AICPU thread sentinel and resolve it against the local hardware topology. - A2A3 selects every usable CPU in the fullest cluster. - A5 selects every usable CPU in the fullest die and excludes the OS-reserved SMT pair from the launch pool. - Keep runtime, affinity, launch, and DFX counts consistent; reject one-thread normal runs and guard A5 register allocations on failure. - Add deterministic placement tests for healthy, partial-good, tie, and insufficient-capacity layouts.
59d5824 to
dfe7e8d
Compare
|
Implemented the initial topology-local auto policy and addressed the actionable review findings:
Local validation:
The full sim run was also attempted, but concurrent a2a3sim/a5sim execution caused shared |
aicpu_thread_num is now derived from the probed AICPU usable count and an arch-default topology instead of a fixed user value that must stay <= usable:
validate_launch_aicpu_num allows 0 (auto). Sim resolves 0 -> arch default (no real OCCUPY/PG); onboard clamps via DeviceRunnerBase::resolve_aicpu_thread_num. hardware.md (a2a3/a5) documents the policy and aligns the "user-accessible 6" note with the active cap (4 a2a3 / 7 a5).
a5 host/device OCCUPY alignment (host 0x1fe=8 vs device 0x1f8=6) is deferred: the new a5 default 5 <= 6 device-usable covers the common case; deeper PG-degraded tolerance needs a5 onboard verification.